import React from 'react'
import { withStyles } from '@material-ui/core/styles'
import { withRouter, Link as RouterLink } from 'react-router-dom'
import ReactMarkdown from 'react-markdown'
import gfm from 'remark-gfm'
import Grid from '@material-ui/core/Grid'
import Button from '@material-ui/core/Button'
import Drawer from '@material-ui/core/Drawer';
import Toolbar from '@material-ui/core/Toolbar'
import Typography from '@material-ui/core/Typography'
import Link from '@material-ui/core/Link'
import Paper from '@material-ui/core/Paper'
import Divider from '@material-ui/core/Divider'
import Chip from '@material-ui/core/Chip'
import Badge from '@material-ui/core/Badge'
import LinearProgress from '@material-ui/core/LinearProgress';
import SvgIcon from '@material-ui/core/SvgIcon'
import CategoryIcon from '@material-ui/icons/Category';
import KeyboardArrowRightIcon from '@material-ui/icons/KeyboardArrowRight';
import KeyboardArrowLeftIcon from '@material-ui/icons/KeyboardArrowLeft';
import Tooltip from '@material-ui/core/Tooltip';
import PersonIcon from '@material-ui/icons/Person'
import AccessTimeIcon from '@material-ui/icons/AccessTime'
import RemoveRedEyeIcon from '@material-ui/icons/RemoveRedEye';
import hljs from 'highlight.js';
import "highlight.js/styles/atom-one-light.css";

import "./github-markdown.css"
import axios, { BLOG_API } from 'API'

const progressStyles = theme => ({
  root: {
    position: "fixed",
    width: "100%",
    zIndex: theme.zIndex.drawer + 1,
    height: "5px",
  },
  colorPrimary: {
    backgroundColor: theme.palette.grey[theme.palette.type === 'light' ? 200 : 700],
  },
  bar: {
    backgroundColor: '#f39c12',
  },
});
const BlueProgress = withStyles(progressStyles)(LinearProgress);


const styles = theme => ({
  container: {
    margin: theme.spacing(4, 0, 8, 0),
    [theme.breakpoints.down('sm')]: {
      padding: theme.spacing(0, 1, 0, 1),
    },
    [theme.breakpoints.up('md')]: {
      padding: theme.spacing(0, 2, 0, 2),
    }
  },
  paper: {
    display: "inline-flex",
    alignItems: "center",
    flexDirection: "column",
    padding: theme.spacing(1),
    margin: theme.spacing(2, 0, 0, 2),
    position: "fixed",
  },
  infoElement: {
    margin: theme.spacing(1, 0, 1, 0),
  },
  tocSideBar: {
    borderLeft: "none",
    maxWidth: "190px",
    padding: theme.spacing(2, 5, 0, 0),
    [theme.breakpoints.down("sm")]: {
      display: "none",   // hide when screen is too small
    }
  },
  anchor: {
    display: "block",
    position: "relative",
    top: props => `-${props.headerHeight + theme.spacing(1)}px`,
    visibility: "hidden",
  },
  tocUl: {
    margin: "0",
    padding: "0",
    listStyle: "none",
  },
  tocContent: {
    display: "block",
    padding: theme.spacing(0.5, 0, 0.5, 0.6),
    fontSize: ".8125rem",
    borderLeft: "3px solid transparent",
    color: theme.palette.grey[400],
  },
  tocH3: {
    paddingLeft: theme.spacing(2),
  },
  activeToc: {
    borderLeftColor: theme.palette.primary.light,
    color: theme.palette.primary.main,
  },
  navButtonContainer: {
    margin: theme.spacing(3, 0, 4, 0),
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
  }
});

/*
 * Since the remark-slug plugins cannot used in react-markdown, so we use
 * a renderer to add id and class
 */

/**
 * 展开标题元素，获取对应的标题文本
 * @param {*} text 递归的参数，调用时为空串
 * @param {*} child 递归的参数，调用时为标题元素
 */
const flatten = (text, child) => {
  return typeof child === 'string'
    ? text + child
    : React.Children.toArray(child.props.children).reduce(flatten, text);
};

/**
 * HeadingRenderer is a custom renderer
 * It parses the heading and attaches an id to it to be used as an anchor
 */
const HeadingRenderer = (classes, props) => {
  const children = React.Children.toArray(props.children);
  const text = children.reduce(flatten, '');
  const HeaderTag = `h${props.level}`;
  return (
    <HeaderTag>
      <div id={`${text}`} className={classes.anchor} />
      <a className={"anchor"} href={`#${text}`}>
        <span className={"octicon octicon-link"} />
      </a>
      {props.children}
    </HeaderTag>
  )
};

const CodeRenderer = (language, value) => {
  return (
    <pre className="tab-size" data-tab-size="4">
      <code className={`language-${language}`} >
        {value}
      </code>
    </pre>
  );
}

/**
 * 处理一条TOC，返回一个Link
 * @param {*} classes useStyles的返回值
 * @param {*} activeHeader 当前活跃的标题
 * @param {*} item 一个标题元素
 */
const singleTOC = (classes, activeHeader, item) => {
  const slug = flatten('', item);
  let className = `${classes.tocContent}`;
  if (item.props.level === 3) {
    className += ` ${classes.tocH3}`;
  }
  if (activeHeader === slug) {
    className += ` ${classes.activeToc}`
  }
  return (
    <Link
      href={`#${slug}`}
      underline="none"
      className={className}
    >
      <Typography variant="inherit">
        {slug}
      </Typography>
    </Link>
  )
}

/**
 * 渲染目录的函数
 * @param {*} classes useStyles的返回值
 * @param {*} activeHeader 当前活跃的标题
 * @param {*} props react-markdown对应renderer的参数
 */
const TOCRenderer = (classes, activeHeader, props) => {
  // 首先我们根据二级标签分组（把所有的3级标签归为前面最近的2级标签中）
  const headings = props.children.reduce((header, item) => {
    if (/heading/gi.test(item.type)) {
      if (item.props.level === 2) {
        header.push([item]);
      } else if (item.props.level === 3) {
        header[header.length - 1].push(item);   // 3级标题加到末尾的数组里
      }
    }
    return header;
  }, []);
  // 每一组是一个array，第一个元素是二级标签，之后全是该组的三级标签
  // 然后把三级标签再划分为该组二级标签下的一个List（多一个缩进）
  return headings.map((item, index) => {
    if (item.length <= 1) {
      return (
        <li className={classes.tocH2} key={`content-${index}`}>
          {singleTOC(classes, activeHeader, item[0])}
        </li>
      )
    } else {
      return (
        <li className={classes.tocH2} key={`content-${index}`}>
          {singleTOC(classes, activeHeader, item[0])}
          <Typography component="ul" className={classes.tocUl}>
            {item.slice(1).map((ele, offset) => (
              <li className={classes.tocH3} key={`content-${offset}`}>
                {singleTOC(classes, activeHeader, ele)}
              </li>
            )
            )}
          </Typography>
        </li>
      )
    }
  })
}



class BlogDetail extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: {},
      activeHeader: "",
      progress: 0,
    }
    const { match, classes } = this.props;   // react-router获取params
    this.handleScroll = this.handleScroll.bind(this);
    this.renderer = {
      heading: (props) => HeadingRenderer(classes, props),
      code: ({ language, value }) => CodeRenderer(language, value),
    };
    this.rocRenderer = {
      root: (props) => TOCRenderer(classes, this.state.activeHeader, props),
    };
  }

  /**
   * 获取当前阅读的标题，逻辑是遍历所有的标题，找到距离当前屏幕最近的标题
   * @param {*} bounds 一个边界值，这里是appbar的高度
   */
  getActiveHeader(bounds) {
    const headers = document.querySelectorAll('.markdown-body h2,h3');
    if (headers.length <= 0) {
      // if content have not been render
      return "";
    }
    const result = [...headers].filter(item => {
      return (item.offsetTop - window.pageYOffset) <= bounds + 20;
    });
    return result.length > 0 ? result[result.length - 1].innerText : headers[0].innerText;
  }

  handleScroll(event) {
    const totalH = document.documentElement.scrollHeight;
    const viewH = window.innerHeight;
    const validH = totalH - viewH;
    this.setState({
      activeHeader: this.getActiveHeader(this.props.headerHeight),
      progress: (window.pageYOffset / validH) * 100,
    })
  }

  fetchData() {
    const id = this.props.match.params.id;
    return axios.request({
      url: `${BLOG_API.blog.url}${id}/`,
      method: BLOG_API.blog.method,
    }).then(response => {
      this.setState({
        data: response.data,
      });
      this.setState({
        activeHeader: this.getActiveHeader(this.props.headerHeight),
      });
      // 高亮代码
      document.querySelectorAll('pre code').forEach((block) => {
        hljs.highlightBlock(block);
      });
    });
  }

  componentDidMount() {
    this.fetchData();
    // 添加处理滑动时间的监听器，为了获取正在阅读的标题
    window.addEventListener('scroll', this.handleScroll);
  }

  componentDidUpdate(preProps) {
    // 当url发生变化后，需要重新获取数据
    if (preProps.match.params.id !== this.props.match.params.id) {
      this.fetchData().then(() => {
        window.scrollTo(0, 0);
      })
    }
  }

  componentWillUnmount() {
    window.removeEventListener('scroll', this.handleScroll)
  }

  renderChip(tips, label, icon, classes) {
    let toUrl = null;
    if (tips === "类别") {
      toUrl = `/blog?category=${label}`
    }
    return (
      <Tooltip placement="right" arrow title={tips}>
        <Chip
          className={classes.infoElement}
          color="primary"
          clickable
          component={toUrl ? RouterLink : "div"}
          to={toUrl}
          icon={icon}
          label={label}
        />
      </Tooltip>
    )
  }

  renderInfo() {
    const category = this.state.data.category ? this.state.data.category.name : "";
    const author = this.state.data.author ? this.state.data.author.nickname : "";
    let createTime = this.state.data.create_time ? this.state.data.create_time : "";
    createTime = createTime.split(' ')[0];
    const validClickNum = this.state.data.page_view ? this.state.data.page_view.valid_click_num : 0;
    const clickNum = this.state.data.page_view ? this.state.data.page_view.click_num : 0;
    const { classes } = this.props;
    return (
      <React.Fragment>
        <Paper elevation={2} className={classes.paper}>
          <Typography variant="subtitle1">博文信息</Typography>
          {this.renderChip("类别", category, (<CategoryIcon />), classes)}
          {this.renderChip("作者", author, (<PersonIcon />), classes)}
          {this.renderChip("创作时间", createTime, (<AccessTimeIcon />), classes)}
          <Tooltip placement="right" arrow title="有效访问量">
            <Badge className={classes.infoElement}
              badgeContent={validClickNum} max={999} showZero color="secondary">
              <RemoveRedEyeIcon />
            </Badge>
          </Tooltip>
          <Tooltip placement="right" arrow title="点击量">
            <Badge className={classes.infoElement}
              badgeContent={clickNum} max={999} showZero color="secondary">
              <SvgIcon viewBox="0 0 1024 1024">
                <path d="M458.286 421.32l86.542 501.754c0 0 63.958-65.842 116.759-111.792l116.1 155.425c4.677 6.317 14.86 6.729 22.641 1.038l49.574-36.225c7.821-5.715 10.369-15.481 5.647-21.796l-112.93-151.18c58.99-23.786 156.15-50.855 156.15-50.855l-440.481-286.368zM318.59 653.020l-128.849 94.176c-19.478 14.262-22.995 41.686-7.578 62.013 15.422 20.269 43.885 25.605 63.299 11.406l128.849-94.176c19.915-14.574 23.243-43.157 7.826-63.421-15.359-20.306-43.655-24.572-63.547-9.998v0zM926.736 264.799c-15.422-20.262-43.264-24.818-63.61-9.961l-127.937 93.581c-20.326 14.86-23.843 42.37-8.447 62.635 15.422 20.262 43.878 25.605 64.207 10.746l127.941-93.581c20.347-14.863 23.243-43.138 7.845-63.42v0zM328.958 167.95c-15.106-19.853-42.905-24.407-63.295-9.523-19.066 13.951-22.995 41.686-7.888 61.537l100.081 131.623c15.111 19.91 43.322 24.091 62.383 10.139 20.326-14.86 23.913-42.308 8.8-62.197l-100.081-131.58zM581.667 324.348c24.818 4.017 47.648-12.691 50.855-37.199l20.080-159.13c3.206-24.464-14.198-47.396-39.392-51.826-24.814-4.017-47.644 12.686-50.855 37.132l-20.138 159.194c-3.105 24.508 14.322 47.402 39.451 51.83v0zM338.854 501.924c2.421-24.573-14.985-47.465-39.387-51.831l-161.245-26.512c-25.664-3.396-48.062 12.978-50.857 37.195-3.7 25.541 13.725 48.435 39.392 51.826l161.241 26.518c24.383 4.24 46.838-12.153 50.855-37.194v0zM338.854 501.924z" p-id="1341"></path>
              </SvgIcon>
            </Badge>
          </Tooltip>
          <Divider component="div" />
        </Paper>
      </React.Fragment>
    )
  }

  renderNavButtons() {
    const { classes } = this.props;
    let previousBtn = <div />;
    let nextBtn = <div />;
    if (this.state.data.previous) {
      previousBtn = (
        <Button
          component={RouterLink}
          to={`/blog/${this.state.data.previous.id}/`}
          startIcon={<KeyboardArrowLeftIcon />}>
          {this.state.data.previous.title}
        </Button>
      );
    }
    if (this.state.data.next) {
      nextBtn = (
        <Button
          component={RouterLink}
          to={`/blog/${this.state.data.next.id}/`}
          startIcon={<KeyboardArrowRightIcon />}>
          {this.state.data.next.title}
        </Button>
      );
    }
    return (
      <div className={classes.navButtonContainer}>
        {previousBtn}
        {nextBtn}
      </div>
    )
  }

  render() {
    const { classes } = this.props;
    /* render */
    return (
      <Grid container>
        <Grid item xs={12}>
          <BlueProgress variant="determinate" value={this.state.progress} />
        </Grid>
        <Grid item xs={false} md={2}>
          {this.renderInfo()}
        </Grid>
        <Grid item xs={12} md={8}>
          <div className={`${classes.container} markdown-body`}>
            <Typography variant="h1">{this.state.data.title}</Typography>
            <ReactMarkdown
              plugins={[gfm]}
              children={this.state.data.content}
              allowDangerousHtml
              renderers={this.renderer} />
          </div>
          <Divider />
          {this.renderNavButtons()}
        </Grid>
        <Grid item xs={false} md={2}>
          <Drawer
            variant="permanent"
            anchor="right"
            classes={{ paperAnchorDockedRight: classes.tocSideBar }}
          >
            <Toolbar />
            <Typography align="center">目录</Typography>
            <Typography component="ul" className={classes.tocUl}>
              <ReactMarkdown children={this.state.data.content} renderers={this.rocRenderer} />
            </Typography>
          </Drawer>
        </Grid>
      </Grid>
    )
  }
}

export default withRouter(withStyles(styles, { withTheme: true })(BlogDetail));